﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using LingDong.ConfigProvider;
using LingDong.IndexEntities.Dictionary;
using LingDong.IndexEntities.Document;
using LingDong.IndexEntities.Helper;
using LingDong.IndexReader;
using LingDong.SerializeProvider;
using NLog;

namespace LingDong.LinkAnalyser
{
    /// <summary>
    /// Interface for LinkAnalyser
    /// A page's rank is consist of three parts: PageRank, SiteRank and Page's quality
    /// </summary>
    public class Ranker
    {
        #region Constructors

        public Ranker()
        {
            logger = LogManager.GetCurrentClassLogger();
            logger.Debug("Ranker Consturct");
        }

        #endregion


        #region Public Methods

        /// <summary>
        /// Compute page rank and save it to index
        /// </summary>
        public void Run()
        {
            try
            {
                using (Index index = new Index())
                {
                    ComputeRanks(index);
                    LogResult(index);
                }

                logger.Debug("Save page rank and update inverted list.");
                UpDateIndexFiles();

            }
            catch (Exception ex)
            {
                logger.Error("Error in Ranker: {0}", ex.Message);
                throw;
            }
        }

        #endregion


        #region Private Methods

        /// <summary>
        /// Do the compute
        /// </summary>
        private void ComputeRanks(Index index)
        {
            logger.Debug("Computing quality rank.");
            double[] quality = PageQuality.GetPageQualityRank(index);

            logger.Debug("Computing page rank.");
            double[] pageRank = new PageRank(index).GetPageRank();

            logger.Debug("Computing site rank.");
            double[] siteRank = new SiteRank(index).GetPageSiteRank();

            Debug.Assert(pageRank.Count() == siteRank.Count());
            Debug.Assert(pageRank.Count() == quality.Count());

            int count = siteRank.Count();
            rank = new double[count];
            for (int i = 0; i < count; i++)
            {
                rank[i] = pageRank[i] + siteRank[i] + quality[i];
            }
        }

        private void LogResult(Index index)
        {
            logger.Trace("Top 10 pages:");
            double bar = rank.OrderByDescending(r => r).ElementAt(10);
            int len = rank.Length;
            for (int i = 0; i < len; i++)
            {
                if (rank[i] >= bar)
                {
                    logger.Trace("ID: {0}, Rank: {1}, URL: {2}",
                                   i, rank[i], index.GetParsedDocument(i).URL);
                }
            }
        }

        private void UpDateIndexFiles()
        {
            SavePageRankToFile();
            UpdateInvertedListByPageRank();
        }

        /// <summary>
        /// Save page rank to index files
        /// </summary>
        private void SavePageRankToFile()
        {
            // deserialize document offset
            Dictionary<int, DocumentOffset> documentOffset;
            using (FileStream fs = new FileStream(IndexNameHelper.GetDocumentIndexFileName(), FileMode.Open))
            {
                documentOffset = Serializer.Deserialize<Dictionary<int, DocumentOffset>>(fs);
            }

            // save new parsed document to a temp file
            string tempFile = Path.GetTempFileName();
            string parsedFile = IndexNameHelper.GetParsedDocumentFileName();
            using (FileStream parsedDocStream = new FileStream(parsedFile, FileMode.Open))
            using (FileStream tempFileStream = new FileStream(tempFile, FileMode.Create))
            {
                int count = rank.Count();
                for (int i = 0; i < count; i++)
                {
                    DocumentOffset offset = documentOffset[i];

                    // load parsed doc
                    parsedDocStream.Position = offset.ParsedDocumentOffset;
                    ParsedDocumentEntity parsedDoc = Serializer.Deserialize<ParsedDocumentEntity>(parsedDocStream);

                    parsedDoc.Rank = rank[i];

                    // save updated parsed doc to the temp file
                    documentOffset[i] = new DocumentOffset(tempFileStream.Position, offset.OriginalDocumentOffset);
                    Serializer.Serialize<ParsedDocumentEntity>(tempFileStream, parsedDoc);
                }
            }

            // move the temp file to index
            File.Delete(parsedFile);
            File.Move(tempFile, parsedFile);

            // save new offset to new index
            using (FileStream fs = new FileStream(IndexNameHelper.GetDocumentIndexFileName(), FileMode.Create))
            {
                Serializer.Serialize<Dictionary<int, DocumentOffset>>(fs, documentOffset);
            }
        }

        /// <summary>
        /// Sort and trim each term's inverted list by page rank
        /// </summary>
        private void UpdateInvertedListByPageRank()
        {
            // deserialize dictionary
            Dictionary<string, DictionaryIndexEntity> termList;
            using (FileStream fs = new FileStream(IndexNameHelper.GetDictionaryFileName(), FileMode.Open))
            {
                termList = Serializer.Deserialize<Dictionary<string, DictionaryIndexEntity>>(fs);
            }

            // save new inverted list to a temp file
            string tempFile = Path.GetTempFileName();
            string invertedListFile = IndexNameHelper.GetInvertedListFileName();
            using (FileStream invertedListStream = new FileStream(invertedListFile, FileMode.Open))
            using (FileStream tempFileStream = new FileStream(tempFile, FileMode.Create))
            {
                foreach (KeyValuePair<string, DictionaryIndexEntity> pair in termList)
                {
                    DictionaryIndexEntity entity = pair.Value;

                    // load inverted list 
                    invertedListStream.Position = entity.InvertedListOffset;
                    Dictionary<int, InvertedListEntity> invertedList = Serializer.Deserialize<Dictionary<int, InvertedListEntity>>(invertedListStream);

                    // only store pages which PR value are in the top InvertedListMaxCount(5000)
                    invertedList = invertedList.OrderByDescending(p => rank[p.Value.DocID]).
                                        Take(ConfigIndex.Default.InvertedListMaxCount).
                                        ToDictionary(p => p.Key, p => p.Value);

                    // save updated inverted list to the temp file
                    entity.InvertedListOffset = tempFileStream.Position;
                    Serializer.Serialize<Dictionary<int, InvertedListEntity>>(tempFileStream, invertedList);
                }
            }

            // move the temp file to inverted list
            File.Delete(invertedListFile);
            File.Move(tempFile, invertedListFile);

            // save new offset to dictionary
            using (FileStream fs = new FileStream(IndexNameHelper.GetDictionaryFileName(), FileMode.Create))
            {
                Serializer.Serialize<Dictionary<string, DictionaryIndexEntity>>(fs, termList);
            }
        }

        #endregion


        #region Private Members

        private double[] rank;
        private Logger logger;

        #endregion
    }
}
